/*******************************************************************************
 * Copyright (c) 2010 European Software Institute - Tecnalia.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     Author - Adrian Noguero (adrian.noguero@tecnalia.com)
 *     
 *******************************************************************************/

package es.esi.gemde.modeltransformator;

import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.net.URI;
import java.net.URL;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map.Entry;
import java.util.Vector;

import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.FileLocator;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Platform;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EClassifier;
import org.eclipse.emf.ecore.EPackage;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.IMemento;
import org.eclipse.ui.XMLMemento;
import org.eclipse.ui.plugin.AbstractUIPlugin;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;

import es.esi.gemde.core.resources.IGemdeResource.ResourceType;
import es.esi.gemde.modeltransformator.service.AbstractJavaTransformation;
import es.esi.gemde.modeltransformator.service.IModelTransformationService;
import es.esi.gemde.modeltransformator.service.ITransformation;
import es.esi.gemde.modeltransformator.service.ITransformationEngine;
import es.esi.gemde.modeltransformator.service.TransformationFactory;
import es.esi.gemde.modeltransformator.service.impl.EngineRepository;
import es.esi.gemde.modeltransformator.service.impl.ModelTransformationService;


/**
 * The activator class controls the plug-in life cycle
 */
public class ModelTransformatorPlugin extends AbstractUIPlugin {

	// The plug-in ID
	public static final String PLUGIN_ID = "es.esi.gemde.modeltransformator";
	
	// Session Data extension point ID
	public static final String SESSION_DATA_EXTENSION_ID = "es.esi.gemde.modeltransformator.sessiondata";
	
	// String constants to save/load repositories
	private final String ROOT = "ModelTransformator";
	private final String TRANSFORMATION = "Transformation";
	private final String NAME = "name";
	private final String ENGINE = "engine";
	private final String URI = "URI";
	private final String URI_VALUE = "value";
	private final String DESCRIPTION = "description";
	private final String ECLASS = "eclass";
	private final String EOBJECT = "eobject";
	private final String EPACKAGE = "epackage";
	private final String INPUT = "Input";
	private final String OUTPUT = "Output";
	private final String OPTION = "Option";
	private final String KEY = "key";
	private final String VALUE = "value";
	
	// The repository file
	private File repositoryFile;
	 

	// The shared instance
	private static ModelTransformatorPlugin plugin;
	
	// The shared instance of the service
	private static IModelTransformationService service = new ModelTransformationService();
	
	
	/**
	 * The constructor
	 */
	public ModelTransformatorPlugin() {
	}

	/*
	 * (non-Javadoc)
	 * @see org.eclipse.ui.plugin.AbstractUIPlugin#start(org.osgi.framework.BundleContext)
	 */
	public void start(BundleContext context) throws Exception {
		super.start(context);
		plugin = this;
		loadEngineExtensions();
		loadSessionExtensions();
		loadRepository();
	}

	/*
	 * (non-Javadoc)
	 * @see org.eclipse.ui.plugin.AbstractUIPlugin#stop(org.osgi.framework.BundleContext)
	 */
	public void stop(BundleContext context) throws Exception {
		plugin = null;
		super.stop(context);
		// Saves the current transformation repository in a file.
		saveRepository();
	}

	/**
	 * Returns the shared instance
	 *
	 * @return the shared instance
	 */
	public static ModelTransformatorPlugin getDefault() {
		return plugin;
	}
	
	public static IModelTransformationService getService() {
		return service;
	}
	
	
	/**
	 * This method delegates to the EngineRepository to load the engines contributed by other plugins.
	 */
	private void loadEngineExtensions () {
		// Delegates the responsibility to the EngineRepository class
		EngineRepository.getInstance().initializeEngines();
	}

	private void loadSessionExtensions() {
		// Get the elements contributing in the extension point
		IConfigurationElement[] config = Platform.getExtensionRegistry().getConfigurationElementsFor(SESSION_DATA_EXTENSION_ID);
		
		for (IConfigurationElement transformation : config) {
			// Add regular transformations
			if (transformation.getName().equals("transformation")) {
				try {
					// Get the attributes of the extension and check them.
					String name = transformation.getAttribute("name");
					String engine = transformation.getAttribute("engine");
					String desc = transformation.getAttribute("description");
					//URI uri = FileLocator.find(this.getBundle(), new Path(transformation.getAttribute("file")), null).toURI();

					ITransformationEngine engineImpl = EngineRepository.getInstance().getEngineByName(engine);
					LinkedHashMap<String, EClass> inputs = new LinkedHashMap<String, EClass>();
					LinkedHashMap<String, EClass> outputs = new LinkedHashMap<String, EClass>();
					Integer counter = 0;
					for (IConfigurationElement input : transformation.getChildren("input")) {
						String iname = input.getAttribute("name");
						String eclass = input.getAttribute("etype");
						String shortEClass = eclass.lastIndexOf('.') == -1 ? eclass : eclass.substring(eclass.lastIndexOf('.')+1);
						String epackage = input.getAttribute("epackage");
						
						EPackage p = null;
						if (epackage != null && !epackage.equals("")) {
							p = EPackage.Registry.INSTANCE.getEPackage(epackage);
						}
						
						if (p == null) {
							// Try to guess the package of the eclass
							for (Object o : EPackage.Registry.INSTANCE.values()) {
								if (o != null && o instanceof EPackage) {
									EClassifier c = ((EPackage)o).getEClassifier(shortEClass);
									if (c != null && c instanceof EClass && ((EClass)c).getInstanceTypeName().equals(eclass)) {
										inputs.put(iname != null ? iname : counter.toString(), (EClass)c);
										break;
									}
								}
							}
							
						}
						else {
							EClass c = (EClass) p.getEClassifier(shortEClass);
							inputs.put(iname != null ? iname : counter.toString(), (EClass)c);
						}
						
					}
					
					counter = 0;
					for (IConfigurationElement output : transformation.getChildren("output")) {
						String iname = output.getAttribute("name");
						String eclass = output.getAttribute("etype");
						String shortEClass = eclass.lastIndexOf('.') == -1 ? eclass : eclass.substring(eclass.lastIndexOf('.')+1);
						String epackage = output.getAttribute("epackage");
						
						EPackage p = null;
						if (epackage != null && !epackage.equals("")) {
							p = EPackage.Registry.INSTANCE.getEPackage(epackage);
						}
						
						if (p == null) {
							// Try to guess the package of the eclass
							for (Object o : EPackage.Registry.INSTANCE.values()) {
								if (o != null && o instanceof EPackage) {
									EClassifier c = ((EPackage)o).getEClassifier(shortEClass);
									if (c != null && c instanceof EClass && ((EClass)c).getInstanceTypeName().equals(eclass)) {
										outputs.put(iname != null ? iname : counter.toString(), (EClass)c);
										break;
									}
								}
							}
							
						}
						else {
							EClass c = (EClass) p.getEClassifier(shortEClass);
							outputs.put(iname != null ? iname : counter.toString(), (EClass)c);
						}
						
					}

					Vector<URI> uris = new Vector<URI>();

					for (IConfigurationElement file : transformation.getChildren("file")) {
						IPath filePath = new Path(file.getAttribute("file"));
						Bundle contributor = Platform.getBundle(file.getContributor().getName());
						URL url = FileLocator.toFileURL(FileLocator.find(contributor, filePath, null));
						uris.add(toCorrectURI(url));
					}
					
					ITransformation t = TransformationFactory.createProtectedTransformation(name, engine, uris, desc, inputs, outputs);
					
					for (IConfigurationElement output : transformation.getChildren("option")) {
						String key = output.getAttribute("key");
						String val = output.getAttribute("val");
						if (key != null && val != null) {
							t.addTransformationOption(key, val);
						}
					}

					// If the parameters are correct with add the transformation to the repository
					if (engineImpl != null && engineImpl.checkTransformationURIValidity(uris) && !service.transformationExists(name)) {
						service.addSessionTransformation(t);
					}
				} catch (Exception ex) {
					ex.printStackTrace();
				}
			}
			// Add Java transformations
			else if (transformation.getName().equals("java_transformation")) {
				try {
					final Object impl = transformation.createExecutableExtension("implementation");
					if (impl instanceof AbstractJavaTransformation) {
						service.addSessionTransformation((AbstractJavaTransformation)impl);
					}
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		}
		
	}

	/**
	 * This method loads the transformation repository from the repository file.
	 */
	private void loadRepository() {
		IPath wsPath = ResourcesPlugin.getWorkspace().getRoot().getLocation();
		
		// Create the directories if needed
		File dir = wsPath.append(".metadata/.plugins").toFile();
		if (!dir.exists() || !dir.isDirectory()) {
			dir.mkdir();
		}
		
		dir = wsPath.append(".metadata/.plugins/" + ModelTransformatorPlugin.PLUGIN_ID).toFile();
		if (!dir.exists() || !dir.isDirectory()) {
			dir.mkdir();
		}
		
		repositoryFile = ResourcesPlugin.getWorkspace().getRoot().getLocation().append(".metadata/.plugins/" + ModelTransformatorPlugin.PLUGIN_ID + "/transformation_repository.xml").toFile();
		// Create the file if it doesn't exist
		if (repositoryFile != null && !repositoryFile.exists()) {
			try {
				repositoryFile.createNewFile();
			} catch (IOException e) {
				MessageDialog.openError(new Shell(), "IOExceptionOccurred", e.getMessage());
				e.printStackTrace();
			}
		}
		// If the file exists, we parse it
		else {
			FileReader reader;
			XMLMemento memento;
			try {
				reader = new FileReader(repositoryFile);
				memento = XMLMemento.createReadRoot(reader);
				
				// Initialize Transformation properties
				IMemento[] transformations = memento.getChildren(TRANSFORMATION);
				for (int i = 0; i < transformations.length; i++) {
					String name = transformations[i].getString(NAME);
					String engine = transformations[i].getString(ENGINE);
					//String uri = transformations[i].getString(URI);
					String description = transformations[i].getString(DESCRIPTION);
					IMemento[] uris = transformations[i].getChildren(URI);
					Vector<URI> uriList = new Vector<URI>();
					for (IMemento uriElem : uris) {
						URI uri = new URI(uriElem.getString(URI_VALUE));
						uriList.add(uri);
					}
					
					IMemento[] inputs = transformations[i].getChildren(INPUT);
					IMemento[] outputs = transformations[i].getChildren(OUTPUT);
					IMemento[] options = transformations[i].getChildren(OPTION);
					
					HashMap<String, EClass> inputObjects = new HashMap<String, EClass>();
					HashMap<String, EClass> outputObjects = new HashMap<String, EClass>();
					Integer counter = 0;
					
					for (IMemento input : inputs) {
						String iname = input.getString(NAME);
						String eclass = input.getString(ECLASS);
						if (eclass == null) {
							eclass = input.getString(EOBJECT);
						}
						
						String shortEClass = eclass.lastIndexOf('.') == -1 ? eclass : eclass.substring(eclass.lastIndexOf('.')+1);
						String epackage = input.getString(EPACKAGE);
						// Check that the read class is subtype of EObject
						EPackage p = EPackage.Registry.INSTANCE.getEPackage(epackage);
						if (p == null) {
							
							// Try to guess the package of the eclass
							for (Object o : EPackage.Registry.INSTANCE.values()) {
								if (o != null && o instanceof EPackage) {
									EClassifier c = ((EPackage)o).getEClassifier(shortEClass);
									if (c != null && c instanceof EClass && ((EClass)c).getInstanceTypeName().equals(eclass)) {
										inputObjects.put(iname != null ? iname : counter.toString(), (EClass)c);
										break;
									}
								}
							}
							
						}
						else {
							EClass c = (EClass) p.getEClassifier(shortEClass);
							inputObjects.put(iname != null ? iname : counter.toString(), (EClass)c);
						}
						counter++;
					}
					
					for (IMemento output : outputs) {
						String iname = output.getString(NAME);
						String eclass = output.getString(ECLASS);
						if (eclass == null) {
							eclass = output.getString(EOBJECT);
						}
						
						String shortEClass = eclass.lastIndexOf('.') == -1 ? eclass : eclass.substring(eclass.lastIndexOf('.')+1);
						String epackage = output.getString(EPACKAGE);
						// Check that the read class is subtype of EObject
						EPackage p = EPackage.Registry.INSTANCE.getEPackage(epackage);
						if (p == null) {
							
							// Try to guess the package of the eclass
							for (Object o : EPackage.Registry.INSTANCE.values()) {
								if (o != null && o instanceof EPackage) {
									EClassifier c = ((EPackage)o).getEClassifier(shortEClass);
									if (c != null && c instanceof EClass && ((EClass)c).getInstanceTypeName().equals(eclass)) {
										outputObjects.put(iname != null ? iname : counter.toString(), (EClass)c);
										break;
									}
								}
							}
							
						}
						else {
							EClass c = (EClass) p.getEClassifier(shortEClass);
							outputObjects.put(iname != null ? iname : counter.toString(), (EClass)c);
						}
						counter++;
					}
					
					ITransformation t = TransformationFactory.createTransformation(name, engine, uriList, description, inputObjects, outputObjects);
					
					for (IMemento opt : options) {
						t.addTransformationOption(opt.getString(KEY), opt.getString(VALUE));
					}
					
					service.addTransformation(t);
				}
				
			}
			catch (Exception e) {
				e.printStackTrace();
			}
		}
		
	}

	/**
	 * This method saves the current repository to the repository file.
	 * It is intended to be called when the service is shutting down.
	 */
	private void saveRepository() {
		XMLMemento memento = XMLMemento.createWriteRoot(ROOT);
		for (ITransformation transform : service.getTransformationRepository()) {
			if (transform.getResourceType().equals(ResourceType.NORMAL)) {
				IMemento child = memento.createChild(TRANSFORMATION);
				child.putString(NAME, transform.getName());
				child.putString(ENGINE, transform.getEngine());
				child.putString(DESCRIPTION, transform.getDescription());
				for (URI uri : transform.getTransformationURIs()) {
					IMemento uriElem = child.createChild(URI);
					uriElem.putString(URI_VALUE, uri.toString());
				}
				for (Entry<String, EClass> c : transform.getInputs().entrySet()) {
					IMemento input = child.createChild(INPUT);
					input.putString(NAME, c.getKey());
					input.putString(ECLASS, c.getValue().getInstanceTypeName());
					input.putString(EPACKAGE, c.getValue().getEPackage().getNsURI());
				}
				for (Entry<String, EClass> c : transform.getOutputs().entrySet()) {
					IMemento input = child.createChild(OUTPUT);
					input.putString(NAME, c.getKey());
					input.putString(ECLASS, c.getValue().getInstanceTypeName());
					input.putString(EPACKAGE, c.getValue().getEPackage().getNsURI());
				}
				for (Entry<String, String> opt : transform.getOptions().entrySet()) {
					IMemento option = child.createChild(OPTION);
					option.putString(KEY, opt.getKey());
					option.putString(VALUE, opt.getValue());
				}
			}
		}
		
		try {
			memento.save(new FileWriter(repositoryFile));
		} catch (IOException e) {
			e.printStackTrace();
		}
		
	}
	
	private URI toCorrectURI(URL url) throws IllegalArgumentException {
		if (!"file".equals(url.getProtocol())) {
			throw new IllegalArgumentException();
		}
	    return new File(url.getFile()).toURI();
	}

}
